#!/bin/bash

set -e
set -u
set -o pipefail

NAME=$(basename ${0})

GENERAL_DEPENDENCIES="
cucumber
devscripts
dnsmasq-base
gawk
git
i18nspector
libav-tools
libcap2-bin
libsikuli-script-java
libvirt-clients
libvirt-daemon-system
libvirt-dev
libvirt0
obfs4proxy
openssh-server
ovmf
pry
python-jabberbot
python-potr
qemu-kvm
qemu-system-x86
ruby-guestfs
ruby-json
ruby-libvirt
ruby-net-irc
ruby-packetfu
ruby-rb-inotify
ruby-rjb
ruby-rspec
ruby-test-unit
seabios
tcpdump
tor
unclutter
virt-viewer
xvfb
"

usage() {
    echo "Usage: $NAME [OPTION]... [--] [CUCUMBER_ARGS]...
Sets up an appropriate environment and invokes cucumber. Note that this script
must be run from the Tails source directory root.

Options for '@product' features:
  --artifacts-base-uri URI
                     Pretend that the artifact is located at URI when printing
                     its location during a scenario failure. This is useful if
                     you intend to serve the artifacts via the web, for
                     instance.
  --capture          Captures failed scenarios into videos stored in the
                     temporary directory (see --tmpdir below) using x264
                     encoding. Requires x264.
  --capture-all      Keep videos for all scenarios, including those that
                     succeed (implies --capture).
  --interactive-debugging
                     On failure, pause test suite until pressing Enter. Also
                     offer the option to open an interactive Ruby shell (pry)
                     in the Cucumber world's context.
  --keep-snapshots   Don't ever delete any snapshots (including ones marked as
                     temporary). This can be a big time saver when debugging new
                     features.
  --retry-find       Print a warning whenever Sikuli fails to find an image
                     and allow *one* retry after pressing ENTER. This is useful
                     for updating outdated images.
  --tmpdir           Directory where various temporary files are written
                     during a test, e.g. VM snapshots and memory dumps,
                     failure screenshots, pcap files and disk images
                     (default is TMPDIR in the environment, and if unset,
                     /tmp/TailsToaster).
  --view             Shows the test session in a windows. Requires x11vnc
                     and xtightvncviewer.
  --vnc-server-only  Starts a VNC server for the test session. Requires x11vnc.
  --iso IMAGE        Test '@product' features using IMAGE.
  --old-iso IMAGE    For some '@product' features (e.g. usb_install) we need
                     an older version of Tails, which this options sets to
                     IMAGE. If none is given, it defaults to the same IMAGE
                     given by --iso, which will be good enough for most testing
                     purposes.

Note that '@source' features has no relevant options.

CUCUMBER_ARGS can be used to specify which features to be run, but also any
cucumber option, although then you must pass \`--\` first to let this wrapper
script know that we're done with *its* options. For debugging purposes, a
'debug' formatter has been added so pretty debugging can be enabled with
\`--format debug\`. You could even combine the default (pretty) formatter with
pretty debugging printed to a file with \`--format pretty --format debug
--out debug.log\`.
"
}

error() {
    echo "${NAME}: error: ${*}" >&2
    usage
    exit 1
}

package_installed() {
    local ret
    set +o pipefail
    if dpkg -s "${1}" 2>/dev/null | grep -q "^Status:.*installed"; then
        ret=0
    else
        ret=1
    fi
    set -o pipefail
    return ${ret}
}

check_dependencies() {
    while [ -n "${1:-}" ]; do
        if ! which "${1}" >/dev/null && ! package_installed "${1}" ; then
            error "'${1}' is missing, please install it and run again."
        fi
        shift
    done
}

display_in_use() {
    [ -e "/tmp/.X${1#:}-lock" ] || [ -e "/tmp/.X11-unix/X${1#:}" ]
}

next_free_display() {
    display_nr=0
    while display_in_use ":${display_nr}"; do
	display_nr=$((display_nr+1))
    done
    echo ":${display_nr}"
}

test_suite_cleanup() {
    (kill -0 ${XVFB_PID} 2>/dev/null && kill ${XVFB_PID}) || /bin/true
}

start_xvfb() {
    Xvfb $TARGET_DISPLAY -screen 0 1024x768x24+32 >/dev/null 2>&1 &
    XVFB_PID=$!
    # Wait for Xvfb to run on TARGET_DISPLAY
    until display_in_use $TARGET_DISPLAY; do
	sleep 1
    done
    echo "Virtual X framebuffer started on display ${TARGET_DISPLAY}"
    # Hide the mouse cursor so it won't mess up Sikuli's screen scanning
    unclutter -display $TARGET_DISPLAY -root -idle 0 >/dev/null 2>&1 &
}

start_vnc_server() {
    check_dependencies x11vnc
    VNC_SERVER_PORT="$(x11vnc -listen localhost -display ${TARGET_DISPLAY} \
                              -bg -nopw -forever 2>&1 | \
                                  grep -m 1 "^PORT=[0-9]\+" | sed 's/^PORT=//')"
    echo "VNC server running on: localhost:${VNC_SERVER_PORT}"
}

start_vnc_viewer() {
    check_dependencies xtightvncviewer
    xtightvncviewer -viewonly localhost:${VNC_SERVER_PORT} 1>/dev/null 2>&1 &
}

capture_session() {
    check_dependencies libvpx1
    echo "Capturing guest display into ${CAPTURE_FILE}"
    avconv -f x11grab -s 1024x768 -r 15 -i ${TARGET_DISPLAY}.0 -an \
        -vcodec libvpx -y "${CAPTURE_FILE}" >/dev/null 2>&1 &
}

# main script

# Unset all environment variables used by this script to pass options
# to cucumber, except TMPDIR since we explicitly want to support
# setting it that way.
ARTIFACTS_BASE_URI=
CAPTURE=
CAPTURE_ALL=
LOG_FILE=
VNC_VIEWER=
VNC_SERVER=
INTERACTIVE_DEBUGGING=
KEEP_SNAPSHOTS=
SIKULI_RETRY_FINDFAILED=
TAILS_ISO=
OLD_TAILS_ISO=

LONGOPTS="artifacts-base-uri:,view,vnc-server-only,capture,capture-all,help,tmpdir:,keep-snapshots,retry-find,iso:,old-iso:,interactive-debugging"
OPTS=$(getopt -o "" --longoptions $LONGOPTS -n "${NAME}" -- "$@")
eval set -- "$OPTS"
while [ $# -gt 0 ]; do
    case $1 in
        --artifacts-base-uri)
            shift
            export ARTIFACTS_BASE_URI="${1}"
            ;;
        --view)
            VNC_VIEWER=yes
            VNC_SERVER=yes
            ;;
        --vnc-server-only)
            VNC_VIEWER=
            VNC_SERVER=yes
            ;;
        --capture)
            check_dependencies x264
            export CAPTURE="yes"
            ;;
        --capture-all)
            check_dependencies x264
            export CAPTURE="yes"
            export CAPTURE_ALL="yes"
            ;;
        --interactive-debugging)
            export INTERACTIVE_DEBUGGING="yes"
            ;;
        --keep-snapshots)
            export KEEP_SNAPSHOTS="yes"
            ;;
        --retry-find)
            export SIKULI_RETRY_FINDFAILED="yes"
            ;;
        --tmpdir)
            shift
            export TMPDIR="$(readlink -f $1)"
            ;;
        --iso)
            shift
            export TAILS_ISO="$(readlink -f $1)"
            ;;
        --old-iso)
            shift
            export OLD_TAILS_ISO="$(readlink -f $1)"
            ;;
        --help)
	    usage
            exit 0
            ;;
        --)
            shift
            break
            ;;
    esac
    shift
done

trap "test_suite_cleanup" EXIT HUP INT QUIT TERM

check_dependencies ${GENERAL_DEPENDENCIES}

TARGET_DISPLAY=$(next_free_display)

start_xvfb

if [ -n "${VNC_SERVER:-}" ]; then
    start_vnc_server
fi
if [ -n "${VNC_VIEWER:-}" ]; then
    start_vnc_viewer
fi

export SIKULI_HOME="/usr/share/java"
export DISPLAY=${TARGET_DISPLAY}

cucumber ${@}
